feat: move admin access group grid to mui#911
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughConsolidates admin-access create/edit into a dialog on the list page, migrates the form to MUI + Formik with validation, updates actions to include pagination metadata and changes saveAdminAccess behavior, updates reducer pagination/total handling and deletion, removes the legacy edit page, and adds comprehensive tests. ChangesAdmin Access Module Dialog-Based Refactor
Sequence Diagram(s)sequenceDiagram
participant Router
participant ListPage as AdminAccessListPage
participant Actions as admin-access-actions
participant Reducer as adminAccessListReducer
participant Dialog as AdminAccessFormPopup
Router->>ListPage: navigate to /app/admin-access | /new | /:id
ListPage->>Actions: getAdminAccesses(term, page, perPage, order, orderDir)
Actions->>Reducer: dispatch REQUEST_ADMIN_ACCESSES + RECEIVE (page/perPage in metadata)
Reducer->>ListPage: state (admin_accesses, totalAdminAccesses, currentPage, perPage)
alt Route /new or /:id
ListPage->>Actions: getAdminAccess(id) if :id
ListPage->>Dialog: open(entity, errors)
else List route
ListPage->>Dialog: close via closeDialog → push /app/admin-access
end
Dialog->>Actions: saveAdminAccess(entity) or deleteAdminAccess(id)
Actions->>Reducer: dispatch ADMIN_ACCESS_ADDED/UPDATED/DELETED
Actions->>ListPage: refresh via getAdminAccesses in .finally()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/layouts/admin-access-layout.js`:
- Around line 30-36: The current Route path (`path={`${match.url}/:access_id?`}`
in the Switch) is too permissive and treats any single-segment suffix as an edit
route; replace it with explicit routes: add a Route for the "new" page using
`${match.url}/new` (rendering AdminAccessListPage or AdminAccessForm as
appropriate) and add a separate Route for numeric IDs using a constrained param
like `${match.url}/:access_id(\\d+)` that renders AdminAccessListPage for
editing; keep the Redirect to `match.url` after those Routes and ensure the
order is: new route, numeric-id route, then Redirect so invalid segments still
fall through to the redirect.
In `@src/pages/admin_access/admin-access-list-page.js`:
- Around line 63-79: The getAdminAccess(accessId).then(() => setOpen(true)) call
can reopen the modal with stale data; modify the effect around
useEffect/getAdminAccess to guard against late responses by sequencing or
cancellation: capture the current accessId/isNew (from match.params.access_id
and a flag from /new) or create a request token/AbortController before calling
getAdminAccess, and when the promise resolves verify the token matches the
latest accessId (and still not isNew) before calling setOpen(true) and applying
fetched data (or abort the fetch). Also ensure resetAdminAccessForm is only
applied for the intended /new route by checking the same guard.
- Around line 106-112: handleDeleteAdminAccess currently performs an optimistic
delete via deleteAdminAccess but never refetches the paginated data, leaving
currentPage pointing at an empty page after deleting the last item; change the
flow so that after deleteAdminAccess resolves (or in its success
callback/promise then), call the pagination refetch function (e.g.,
fetchAdminAccessPage or refetchAdminAccess) for the currentPage, and if the
returned page is empty and currentPage > 1, decrement currentPage and refetch
the previous page; update the state that holds currentPage and the page data
accordingly instead of relying only on the reducer’s local filter.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f9c84e8b-8dbe-47a6-856f-4041b0d8ca43
📒 Files selected for processing (8)
src/actions/admin-access-actions.jssrc/components/forms/admin-access-form.jssrc/layouts/admin-access-layout.jssrc/pages/admin_access/__tests__/admin-access-list-page.test.jssrc/pages/admin_access/admin-access-list-page.jssrc/pages/admin_access/edit-admin-access-page.jssrc/reducers/admin_access/__tests__/admin-access-list-reducer.test.jssrc/reducers/admin_access/admin-access-list-reducer.js
💤 Files with no reviewable changes (1)
- src/pages/admin_access/edit-admin-access-page.js
| useEffect(() => { | ||
| const { access_id: accessId } = match.params; | ||
| const isNew = /\/new$/.test(history.location.pathname); | ||
|
|
||
| this.state = {}; | ||
| } | ||
| if (isNew) { | ||
| resetAdminAccessForm(); | ||
| setOpen(true); | ||
| return; | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| this.props.getAdminAccesses(); | ||
| } | ||
| if (accessId) { | ||
| getAdminAccess(accessId).then(() => setOpen(true)); | ||
| return; | ||
| } | ||
|
|
||
| handleEdit(admin_access_id) { | ||
| const { history } = this.props; | ||
| history.push(`/app/admin-access/${admin_access_id}`); | ||
| } | ||
| setOpen(false); | ||
| }, [match.params.access_id, history.location.pathname]); |
There was a problem hiding this comment.
Guard against stale edit loads.
getAdminAccess(accessId).then(() => setOpen(true)) has no stale-request check. If the user closes the dialog or switches from /app/admin-access/1 to /app/admin-access/new before the fetch finishes, the late response can reopen the modal and repopulate the form with the old record. This needs request sequencing/cancellation, or at least a route/id guard before applying the result.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/admin_access/admin-access-list-page.js` around lines 63 - 79, The
getAdminAccess(accessId).then(() => setOpen(true)) call can reopen the modal
with stale data; modify the effect around useEffect/getAdminAccess to guard
against late responses by sequencing or cancellation: capture the current
accessId/isNew (from match.params.access_id and a flag from /new) or create a
request token/AbortController before calling getAdminAccess, and when the
promise resolves verify the token matches the latest accessId (and still not
isNew) before calling setOpen(true) and applying fetched data (or abort the
fetch). Also ensure resetAdminAccessForm is only applied for the intended /new
route by checking the same guard.
|
|
||
| dispatch( | ||
| showMessage(successMessage, () => { | ||
| history.push(`/app/admin-access/${payload.response.id}`); |
There was a problem hiding this comment.
redirectOnCreate if should go here, if not the noAlert flag is redundant
| ? `${member.first_name} ${member.last_name} (${member.email})` | ||
| : `${member.first_name} ${member.last_name} (${member.id})`; | ||
| }} | ||
| : `${member.first_name} ${member.last_name} (${member.id})`} |
There was a problem hiding this comment.
if the only thing that changes is the parenthesis, then the ternary condition should go there
| path={`${match.url}/:access_id?`} | ||
| component={AdminAccessListPage} | ||
| /> | ||
| <Redirect to={match.url} /> |
There was a problem hiding this comment.
before the routes would only match on / or digit, or new. Now it matches everything , for example app/admin-access/santi. it is not equivalent to what we had
| this.handleDeleteAdminAccess = this.handleDeleteAdminAccess.bind(this); | ||
| useEffect(() => { | ||
| const { access_id: accessId } = match.params; | ||
| const isNew = /\/new$/.test(history.location.pathname); |
There was a problem hiding this comment.
so what if the url is app/admin-access/santi/new ?
| const totalItems = | ||
| typeof totalAdminAccesses === "number" | ||
| ? totalAdminAccesses | ||
| : admin_accesses.length; |
There was a problem hiding this comment.
admin_accesses is paginated, the length of this will rarely be the total
2c47f00 to
e24b38c
Compare
ee5e6d9 to
c1ff274
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/pages/admin_access/__tests__/admin-access-list-page.test.js (1)
43-45: ⚡ Quick winMock
onDeletewith the row id, not the full row, to match the realMuiTablecontract.The real
MuiTableinvokesonDeletewith the primitive row id, whereasonEditreceives the full row. This mock firesonDelete(row), so the delete test only exercises the object branch ofhandleDeleteAdminAccess(typeof rowOrId === "object") while production hits the id branch. Passingrow.idkeeps the test green and faithful to the contract.♻️ Align mock with the real callback payload
- <button type="button" onClick={() => onDelete(row)}> + <button type="button" onClick={() => onDelete(row.id)}> delete </button>Based on learnings:
onDeleteis called with the primitive row identifier (e.g.,id/rowId), not the full row object, whileonEditandonSelectreceive the full row object.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/admin_access/__tests__/admin-access-list-page.test.js` around lines 43 - 45, The test mock currently calls onDelete(row) but the real MuiTable passes the primitive row id; change the mock delete button to call onDelete(row.id) so the test exercises the id branch in handleDeleteAdminAccess (keep onEdit as-is since it expects the full row object). Update the mock in the test file to pass the row.id identifier to onDelete and verify assertions still target the id-based behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/forms/admin-access-form.js`:
- Around line 33-44: AdminAccessForm currently ignores server-side validation
errors because its props only include { entity, onSubmit, isSaving }; update the
component to accept an errors prop and, inside the component (after useFormik),
watch for changes to that errors prop and call formik.setErrors(errors) and set
relevant fields touched (e.g.,
formik.setTouched(Object.fromEntries(Object.keys(errors||{}).map(k=>[k,true]))))
so Formik displays backend field errors; reference AdminAccessForm, useFormik,
formik.setErrors and formik.setTouched when adding the effect.
---
Nitpick comments:
In `@src/pages/admin_access/__tests__/admin-access-list-page.test.js`:
- Around line 43-45: The test mock currently calls onDelete(row) but the real
MuiTable passes the primitive row id; change the mock delete button to call
onDelete(row.id) so the test exercises the id branch in handleDeleteAdminAccess
(keep onEdit as-is since it expects the full row object). Update the mock in the
test file to pass the row.id identifier to onDelete and verify assertions still
target the id-based behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4f222801-0811-48de-a064-e78c82e90c61
📒 Files selected for processing (8)
src/actions/__tests__/admin-access-actions.test.jssrc/actions/admin-access-actions.jssrc/components/forms/admin-access-form.jssrc/i18n/en.jsonsrc/layouts/admin-access-layout.jssrc/pages/admin_access/__tests__/admin-access-list-page.test.jssrc/pages/admin_access/admin-access-list-page.jssrc/pages/admin_access/edit-admin-access-page.js
💤 Files with no reviewable changes (1)
- src/pages/admin_access/edit-admin-access-page.js
✅ Files skipped from review due to trivial changes (1)
- src/i18n/en.json
c1ff274 to
9e8890d
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/pages/admin_access/admin-access-list-page.js (2)
77-80:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHandle
getAdminAccessrejection.The promise chain lacks a
.catch. If the fetch fails (e.g. invalidaccess_id), this produces an unhandled rejection and the dialog silently never opens, leaving the user on a dead URL. Add a catch that closes/returns to the list.🛡️ Proposed fix
if (accessId) { - getAdminAccess(accessId).then(() => setOpen(true)); + getAdminAccess(accessId) + .then(() => setOpen(true)) + .catch(() => { + history.push("/app/admin-access"); + }); return; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/admin_access/admin-access-list-page.js` around lines 77 - 80, The current call to getAdminAccess(accessId).then(() => setOpen(true)) lacks error handling; wrap it with a .catch to handle rejection by closing the dialog and returning the user to the list. Specifically, update the branch that calls getAdminAccess(accessId) so failures call setOpen(false) (or ensure the dialog remains closed) and perform a navigation back to the admin-access list (e.g., via the app's router helper or history push/replace) and/or show a user-facing error; keep getAdminAccess and setOpen as the referenced symbols when implementing the .catch handler.
150-162:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd a
renderformatter for thesummits/memberscolumns
getAdminAccessesexpandssummits/membersand requestssummits.nameplusmembers.first_name/last_name, so thesecolumnKeys receive arrays of objects. Since the columns define norenderformatter, the table renders the rawrow[col.columnKey](invalid React children /[object Object]). Add arenderthat maps to display strings.🐛 Proposed fix (confirm the column renderer prop name for MuiTable)
{ columnKey: "title", header: T.translate("admin_access.title"), sortable: true }, - { columnKey: "summits", header: T.translate("admin_access.summits") }, - { columnKey: "members", header: T.translate("admin_access.members") } + { + columnKey: "summits", + header: T.translate("admin_access.summits"), + render: (row) => (row.summits || []).map((s) => s.name).join(", ") + }, + { + columnKey: "members", + header: T.translate("admin_access.members"), + render: (row) => + (row.members || []) + .map((m) => `${m.first_name} ${m.last_name}`) + .join(", ") + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/admin_access/admin-access-list-page.js` around lines 150 - 162, The summits and members table columns in the columns array created in useMemo lack render formatters, so the table tries to render arrays of objects (causing [object Object] or invalid React children); update the column definitions for columnKey "summits" and "members" to add a render function (or the MuiTable column renderer prop name used in this codebase) that maps each summit object to summit.name (joined by commas) and each member object to a display name like `${first_name} ${last_name}` (also joined by commas) before returning the string/JSX for the cell; locate the columns definition in admin-access-list-page.js (the useMemo that defines columns) and add these renderers to those two column entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/forms/admin-access-form.js`:
- Around line 84-88: The getOptionLabel callback uses
member.hasOwnProperty("email"), which triggers no-prototype-builtins warnings
and can fail for objects with a null prototype or shadowed methods; update the
check in getOptionLabel to use Object.prototype.hasOwnProperty.call(member,
"email") (or an equivalent safe check like Boolean(member.email)) so the label
generation for the member (first_name, last_name, email/id) remains identical
but avoids prototype-builtins issues.
---
Outside diff comments:
In `@src/pages/admin_access/admin-access-list-page.js`:
- Around line 77-80: The current call to getAdminAccess(accessId).then(() =>
setOpen(true)) lacks error handling; wrap it with a .catch to handle rejection
by closing the dialog and returning the user to the list. Specifically, update
the branch that calls getAdminAccess(accessId) so failures call setOpen(false)
(or ensure the dialog remains closed) and perform a navigation back to the
admin-access list (e.g., via the app's router helper or history push/replace)
and/or show a user-facing error; keep getAdminAccess and setOpen as the
referenced symbols when implementing the .catch handler.
- Around line 150-162: The summits and members table columns in the columns
array created in useMemo lack render formatters, so the table tries to render
arrays of objects (causing [object Object] or invalid React children); update
the column definitions for columnKey "summits" and "members" to add a render
function (or the MuiTable column renderer prop name used in this codebase) that
maps each summit object to summit.name (joined by commas) and each member object
to a display name like `${first_name} ${last_name}` (also joined by commas)
before returning the string/JSX for the cell; locate the columns definition in
admin-access-list-page.js (the useMemo that defines columns) and add these
renderers to those two column entries.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8e636853-e8b9-4f12-9db6-0f33466cbd6e
📒 Files selected for processing (7)
src/actions/__tests__/admin-access-actions.test.jssrc/actions/admin-access-actions.jssrc/components/forms/admin-access-form.jssrc/i18n/en.jsonsrc/layouts/admin-access-layout.jssrc/pages/admin_access/__tests__/admin-access-list-page.test.jssrc/pages/admin_access/admin-access-list-page.js
✅ Files skipped from review due to trivial changes (1)
- src/i18n/en.json
🚧 Files skipped from review as they are similar to previous changes (3)
- src/layouts/admin-access-layout.js
- src/pages/admin_access/tests/admin-access-list-page.test.js
- src/actions/tests/admin-access-actions.test.js
| getOptionLabel={(member) => | ||
| member.hasOwnProperty("email") | ||
| ? `${member.first_name} ${member.last_name} (${member.email})` | ||
| : `${member.first_name} ${member.last_name} (${member.id})`; | ||
| }} | ||
| onChange={this.handleChange} | ||
| multi={true} | ||
| : `${member.first_name} ${member.last_name} (${member.id})` | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -H -t f -g '.eslintrc*' | xargs -r cat
rg -nP "no-prototype-builtins"
rg -nP "\.hasOwnProperty\(" --type=js -g '!**/node_modules/**'Repository: fntechgit/summit-admin
Length of output: 31424
Avoid calling member.hasOwnProperty("email") in getOptionLabel.
no-prototype-builtins is enabled in this repo (set to "warn"), and this exact pattern also appears in other files. Using Object.prototype.hasOwnProperty.call avoids issues with null-prototype/shadowed hasOwnProperty and keeps lint clean.
♻️ Proposed fix
- getOptionLabel={(member) =>
- member.hasOwnProperty("email")
+ getOptionLabel={(member) =>
+ Object.prototype.hasOwnProperty.call(member, "email")
? `${member.first_name} ${member.last_name} (${member.email})`
: `${member.first_name} ${member.last_name} (${member.id})`
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| getOptionLabel={(member) => | |
| member.hasOwnProperty("email") | |
| ? `${member.first_name} ${member.last_name} (${member.email})` | |
| : `${member.first_name} ${member.last_name} (${member.id})`; | |
| }} | |
| onChange={this.handleChange} | |
| multi={true} | |
| : `${member.first_name} ${member.last_name} (${member.id})` | |
| } | |
| getOptionLabel={(member) => | |
| Object.prototype.hasOwnProperty.call(member, "email") | |
| ? `${member.first_name} ${member.last_name} (${member.email})` | |
| : `${member.first_name} ${member.last_name} (${member.id})` | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/forms/admin-access-form.js` around lines 84 - 88, The
getOptionLabel callback uses member.hasOwnProperty("email"), which triggers
no-prototype-builtins warnings and can fail for objects with a null prototype or
shadowed methods; update the check in getOptionLabel to use
Object.prototype.hasOwnProperty.call(member, "email") (or an equivalent safe
check like Boolean(member.email)) so the label generation for the member
(first_name, last_name, email/id) remains identical but avoids
prototype-builtins issues.
| this.props.getAdminAccesses(); | ||
| } | ||
| if (accessId) { | ||
| getAdminAccess(accessId).then(() => setOpen(true)); |
There was a problem hiding this comment.
getAdminAccess(accessId) has no .catch() here. If the API returns an error (404, network failure, auth expiry), the dialog never opens and the URL stays at /app/admin-access/:id with no recovery path. authErrorHandler shows a toast but does not navigate back to the list.
Suggested fix: add .catch(() => history.push("/app/admin-access")) so a failed load redirects back to the list.
| const [searchTerm, setSearchTerm] = useState(term || ""); | ||
| const [open, setOpen] = useState(false); | ||
| const [isSaving, setIsSaving] = useState(false); | ||
| const isSavingRef = useRef(false); |
There was a problem hiding this comment.
This deviates from the project's popup/dialog pattern (summit-admin-popup-dialog-pattern.md), which uses only useState for the saving guard. In React 16, closeDialog is a fresh closure on every render and already sees the current isSaving value — the ref adds no correctness benefit but creates two sources of truth that can drift if the component unmounts mid-save.
Suggested fix: remove isSavingRef and check isSaving directly in closeDialog, consistent with every other popup in the codebase.
| }); | ||
|
|
||
| export default connect(mapStateToProps, { | ||
| getSummitById, |
There was a problem hiding this comment.
getSummitById is wired into connect but neither destructured in the component's props nor called anywhere — missed cleanup from the refactor. Safe to remove from the import and the connect call.
| ); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
The tests cover the happy-path dispatch sequence (ADDED/UPDATED → STOP_LOADING) but there is no test for the save failure path. A test that rejects postRequest/putRequest would confirm that stopLoading still fires via .finally() — a future refactor could silently break that guarantee.
| mockSaveAdminAccess.mockReturnValue(() => Promise.resolve()); | ||
| }); | ||
|
|
||
| describe("rendering and navigation", () => { |
There was a problem hiding this comment.
Missing coverage for several failure/navigation scenarios:
getAdminAccessfailure path: whenmockGetAdminAccessrejects, the dialog should not open and the component should navigate back to/app/admin-access.saveAdminAccessrejection path: verifyingstopLoadingfires via.finally()is tested in the action suite, but the component-level behavior (dialog stays open, buttons re-enabled, list does not refresh) has no equivalent test here.- URL effect reset: no test verifies that navigating away from a detail route (e.g. from
/app/admin-access/1back to/app/admin-access) closes the dialog.
smarcet
left a comment
There was a problem hiding this comment.
@priscila-moneo please review comments
9e8890d to
90f9d74
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/admin_access/__tests__/admin-access-list-page.test.js`:
- Around line 104-109: The test currently never sets baseState.loading so the
"in-flight save disables controls" assertions don't exercise the real guard;
update the test to drive loading by either (a) calling renderPage with a store
state where baseState.loading is > 0 (e.g. 1) to simulate an in-flight save, or
(b) mock the save thunk to dispatch the loading start action before resolving so
the component sees loading > 0; locate renderPage and mockStore usage and the
save thunk mock to implement one of these changes (adjust renderPage invocation
or the thunk mock) so the disabled-state assertions run against an actual
loading state.
In `@src/pages/admin_access/admin-access-form-popup.js`:
- Around line 1-2: The component currently relies on the global loading prop
which is set after an async token fetch, allowing rapid repeated submits; add a
component-scoped in-flight guard (e.g., local state boolean isSaving or
isSubmitting) and use it at the start of the submit handler to short-circuit
duplicate calls to saveAdminAccess; set isSaving=true immediately before
initiating the async/token fetch, reset it in a finally block after
saveAdminAccess completes or errors, and keep the existing checks against the
global loading prop (reference the submit handler that calls saveAdminAccess,
the saveAdminAccess dispatch, and the loading prop) so multiple rapid clicks
cannot trigger concurrent saves.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 47d29561-f28d-407e-8618-63bf77691dd6
📒 Files selected for processing (11)
src/actions/__tests__/admin-access-actions.test.jssrc/actions/admin-access-actions.jssrc/components/forms/admin-access-form.jssrc/i18n/en.jsonsrc/layouts/admin-access-layout.jssrc/pages/admin_access/__tests__/admin-access-list-page.test.jssrc/pages/admin_access/admin-access-form-popup.jssrc/pages/admin_access/admin-access-list-page.jssrc/pages/admin_access/edit-admin-access-page.jssrc/reducers/admin_access/__tests__/admin-access-list-reducer.test.jssrc/reducers/admin_access/admin-access-list-reducer.js
💤 Files with no reviewable changes (1)
- src/pages/admin_access/edit-admin-access-page.js
🚧 Files skipped from review as they are similar to previous changes (7)
- src/reducers/admin_access/tests/admin-access-list-reducer.test.js
- src/i18n/en.json
- src/actions/tests/admin-access-actions.test.js
- src/layouts/admin-access-layout.js
- src/components/forms/admin-access-form.js
- src/reducers/admin_access/admin-access-list-reducer.js
- src/actions/admin-access-actions.js
a8ecc81 to
cf34c55
Compare
smarcet
left a comment
There was a problem hiding this comment.
@priscila-moneo there are UX issues and we need to get rid of the sweet alert
in favor of snackbar handler
smarcet
left a comment
There was a problem hiding this comment.
Missing test coverage for three /sort/search paths identified in review.
| ); | ||
|
|
||
| return MockMuiTable; | ||
| }); |
There was a problem hiding this comment.
Missing: handleSort not reachable from this mock.
The MockMuiTable destructures onEdit, onDelete, onPerPageChange but omits onSort. No test can exercise handleSort → getAdminAccesses(term, currentPage, perPage, key, dir) without exposing it here.
Suggested addition to the mock:
<button type="button" onClick={() => onSort('title', -1)}>
sort-col
</button>And a corresponding test asserting mockGetAdminAccesses was called with the new sort column/direction.
| __esModule: true, | ||
| default: () => <input placeholder="search" /> | ||
| }) | ||
| ); |
There was a problem hiding this comment.
Missing: handleSearch is never triggered from this mock.
The MuiSearchInput mock renders a plain <input> but doesn't wire up the onSearch prop, so handleSearch → getAdminAccesses(value, DEFAULT_CURRENT_PAGE, ...) is never exercised.
Suggested addition to the mock:
default: ({ onSearch }) => (
<input placeholder="search" onChange={(e) => onSearch(e.target.value)} />
)And a test typing into the input and asserting mockGetAdminAccesses was called with the new search term and page reset to 1.
| expect(mockGetAdminAccesses.mock.calls.length).toBeGreaterThan( | ||
| callsBefore | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Missing: nextPage decrement branch never triggered.
The production handleDeleteAdminAccess has this branch:
const nextPage =
admin_accesses.length === 1 && currentPage > 1
? currentPage - 1
: currentPage;Both existing delete tests use baseListState where currentPage: 1, so the decrement path (currentPage - 1) is never reached.
Suggested test to add after this one:
it('decrements page when deleting the last item on a page > 1', async () => {
const store = mockStore({
adminAccessListState: { ...baseListState, currentPage: 2, admin_accesses: [{ id: 1, title: 'G' }] },
adminAccessState: baseFormState
});
// render, click delete, flush
// assert mockGetAdminAccesses was last called with page = 1
});| const handleSave = (adminAccessEntity) => { | ||
| if (isSaving) return; | ||
| setIsSaving(true); | ||
| saveAdminAccess(adminAccessEntity) |
There was a problem hiding this comment.
This deviates from the dominant codebase pattern. Surveying the codebase, 6 out of 7 popup implementations keep isSaving in the popup (correct here), but the save action and the post-save reload chain belong in the parent list page — not inside the popup.
The pattern used by media-file-type-list-page, form-template-list-page, and others:
// AdminAccessListPage — parent owns the save + reload chain
const handleSave = (entity) =>
saveAdminAccess(entity)
.then(() => getAdminAccesses(term, currentPage, perPage, order, orderDir));
// passed as a single prop
<AdminAccessFormPopup onSave={handleSave} onClose={closeDialog} />// AdminAccessFormPopup — popup only calls onSave and handles isSaving
const handleSave = (entity) => {
if (isSaving) return;
setIsSaving(true);
onSave(entity)
.then(() => onClose())
.catch(() => {})
.finally(() => setIsSaving(false));
};With this shape: saveAdminAccess and its Redux connect move to the parent, the popup receives a single onSave prop (instead of onSaved + onClose), and isSaving stays in the popup — which is correct per the dominant pattern.
Signed-off-by: Priscila Moneo <priscila_moneo@hotmail.com.ar>
cf34c55 to
4a3b2e5
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/actions/admin-access-actions.js`:
- Around line 94-95: The snackbarErrorHandler may be swallowing promise
rejections instead of propagating them, causing error handling chains to break.
In the getAdminAccess and deleteAdminAccess functions, replace the `.then(() =>
stopLoading())` pattern with a `.finally(() => stopLoading())` block to ensure
the loading state is cleared regardless of request success or failure, similar
to how saveAdminAccess already implements this pattern. Verify that
snackbarErrorHandler explicitly returns a rejected Promise after displaying the
error message so that downstream catch handlers in AdminAccessListPage and other
callers can properly handle the rejection chain.
In `@src/components/forms/admin-access-form.js`:
- Around line 51-57: The useEffect hook in admin-access-form.js returns early
when serverErrors is empty without clearing previously set Formik errors,
causing stale error messages to persist even after Redux resets the errors.
Instead of returning early when serverErrors is empty, call formik.setErrors({})
and formik.setTouched({}) to explicitly clear Formik's error and touched state,
ensuring it stays synchronized with the Redux state being reset.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b74d7e8a-5671-49b0-bc6b-bb914b8be1ad
📒 Files selected for processing (11)
src/actions/__tests__/admin-access-actions.test.jssrc/actions/admin-access-actions.jssrc/components/forms/admin-access-form.jssrc/i18n/en.jsonsrc/layouts/admin-access-layout.jssrc/pages/admin_access/__tests__/admin-access-list-page.test.jssrc/pages/admin_access/admin-access-form-popup.jssrc/pages/admin_access/admin-access-list-page.jssrc/pages/admin_access/edit-admin-access-page.jssrc/reducers/admin_access/__tests__/admin-access-list-reducer.test.jssrc/reducers/admin_access/admin-access-list-reducer.js
💤 Files with no reviewable changes (1)
- src/pages/admin_access/edit-admin-access-page.js
🚧 Files skipped from review as they are similar to previous changes (5)
- src/reducers/admin_access/tests/admin-access-list-reducer.test.js
- src/i18n/en.json
- src/actions/tests/admin-access-actions.test.js
- src/layouts/admin-access-layout.js
- src/reducers/admin_access/admin-access-list-reducer.js
| snackbarErrorHandler, | ||
| { order, orderDir, term, page, perPage } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 1) Inspect snackbar/auth error handler implementations.
fd -i 'base-actions.js' | xargs -r rg -n -A20 -B5 'snackbarErrorHandler|authErrorHandler'
# 2) Inspect request helper semantics (resolve vs reject on errors).
rg -n -A30 -B10 --type=js 'export const getRequest|function getRequest|const getRequest'
rg -n -A30 -B10 --type=js 'export const postRequest|export const putRequest|export const deleteRequest'
# 3) Re-check caller expectation in list page.
rg -n -A8 -B4 --type=js 'getAdminAccess\(' src/pages/admin_access/admin-access-list-page.jsRepository: fntechgit/summit-admin
Length of output: 2009
🏁 Script executed:
# Check request-helper or actions utility to understand error handler contract
rg -n -A15 'error.*handler|errorHandler' src/utils/request-helper.js src/utils/actions.js 2>/dev/null | head -100
# Inspect the actual admin-access-actions.js to see full context of lines 94-95, 118-119, 190
cat -n src/actions/admin-access-actions.js | sed -n '85,110p'
cat -n src/actions/admin-access-actions.js | sed -n '110,130p'
cat -n src/actions/admin-access-actions.js | sed -n '185,195p'
# Find how request utilities handle error callbacks (getRequest, postRequest, etc.)
fd -type f 'request-helper.js' | xargs -r cat -n | head -150Repository: fntechgit/summit-admin
Length of output: 2321
🏁 Script executed:
# Find request-helper or actions utility files
find . -name "*request*" -o -name "*actions*" | grep -E "utils|lib" | head -20
# Look for getRequest implementation to understand error handler contract
rg -n "export.*getRequest|function getRequest" | head -20
# Check other error handler implementations for comparison
rg -n "ErrorHandler.*=" -A5 | head -100Repository: fntechgit/summit-admin
Length of output: 5481
🏁 Script executed:
# Check package.json for openstack-uicore-foundation version
cat package.json | grep -A2 -B2 "openstack-uicore-foundation"
# Look for any local request helper/wrapper that might provide more context
find src -name "*request*" -o -name "*helper*" | grep -v node_modules
# Check admin-access-list-page to understand caller expectations
cat -n src/pages/admin_access/admin-access-list-page.js | sed -n '55,80p'Repository: fntechgit/summit-admin
Length of output: 1720
🏁 Script executed:
# Look for any error handlers that explicitly return rejected promises or throw
rg -n "return.*reject|throw.*Error|Promise.reject" src/actions/ | head -30
# Check if any error handlers dispatch a rejection or error state
rg -n "ERROR|FAILURE" src/actions/admin-access-actions.js
# Look for patterns where handlers might suppress errors
rg -n "catch.*=>" src/actions/ | head -20Repository: fntechgit/summit-admin
Length of output: 2140
🌐 Web query:
openstack-uicore-foundation getRequest error handler Promise rejection semantics
💡 Result:
The library openstack-uicore-foundation is a collection of ReactJS and Redux components designed for the OpenStack marketing website [1][2]. It is not a general-purpose OpenStack API client or a low-level SDK for managing OpenStack infrastructure [1][2]. There is no evidence of a "getRequest" function or specific "Promise rejection semantics" within the openstack-uicore-foundation repository [1][2]. Queries regarding "getRequest" and error handling in an OpenStack context typically relate to other projects, such as: - The OpenStack SDK (for Python, Rust, or other languages), which provides standardized methods for API interaction and error handling [3][4][5]. - OpenStack API specifications regarding structured error responses [6]. - Other frameworks like Uvicore (a separate Python framework) that explicitly manage exception handlers [7]. If you are encountering a "getRequest" error, please verify the library or SDK you are using to interact with the OpenStack API, as it is likely not openstack-uicore-foundation [1][2][3]. For standard JavaScript/TypeScript applications interacting with OpenStack APIs, standard Promise-based error handling (using.catch or try/catch blocks with async/await) is the recommended approach to managing rejections [1][2].
Citations:
- 1: https://github.com/OpenStackweb/openstack-uicore-foundation
- 2: https://www.npmjs.com/package/openstack-uicore-foundation
- 3: https://docs.rs/openstack_sdk_core/latest/openstack_sdk_core/api/index.html
- 4: https://github.com/openstack/openstacksdk/blob/master/openstack/utils.py
- 5: https://github.com/openstack/openstacksdk/blob/master/examples/connect.py
- 6: https://specs.openstack.org/openstack/api-sig/guidelines/errors.html
- 7: https://uvicore.io/http/exceptions/handlers/
🏁 Script executed:
# Check admin-access-actions test file for mocking patterns
cat -n src/actions/__tests__/admin-access-actions.test.js | head -80
# Look for other uses of getRequest to see error handler patterns
rg -n "getRequest" src/actions/ -B3 -A3 | head -150
# Check if there's documentation or type definitions for getRequest
find . -path ./node_modules -prune -o -name "*.d.ts" -exec grep -l "getRequest" {} \; 2>/dev/null | head -10Repository: fntechgit/summit-admin
Length of output: 11707
🏁 Script executed:
# Get the complete saveAdminAccess function
cat -n src/actions/admin-access-actions.js | sed -n '128,175p'Repository: fntechgit/summit-admin
Length of output: 1723
🏁 Script executed:
# Get the complete test case that checks rejection behavior
cat -n src/actions/__tests__/admin-access-actions.test.js | sed -n '77,95p'Repository: fntechgit/summit-admin
Length of output: 837
🏁 Script executed:
# Search for authErrorHandler implementation in the codebase
rg -n "export const authErrorHandler|export function authErrorHandler" -A10
# Also check if there's a local implementation or if it's all from openstack-uicore-foundation
rg -n "authErrorHandler" src/ --type=js | head -20Repository: fntechgit/summit-admin
Length of output: 1273
Confirm error handler doesn't suppress rejections in the request chain.
snackbarErrorHandler dispatches a message but doesn't explicitly return a rejected Promise. Verify that getRequest, putRequest, and deleteRequest from openstack-uicore-foundation properly re-throw or re-reject after invoking the error handler; otherwise:
AdminAccessListPagerelies ongetAdminAccess(accessId).catch(...)to redirect on failure, which won't trigger if the rejection is swallowedgetAdminAccessanddeleteAdminAccessuse.then(() => stopLoading())which won't execute on error, leaving loading state stuck
Consider using .finally() pattern (as in saveAdminAccess) to ensure stopLoading() always executes regardless of request outcome.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/actions/admin-access-actions.js` around lines 94 - 95, The
snackbarErrorHandler may be swallowing promise rejections instead of propagating
them, causing error handling chains to break. In the getAdminAccess and
deleteAdminAccess functions, replace the `.then(() => stopLoading())` pattern
with a `.finally(() => stopLoading())` block to ensure the loading state is
cleared regardless of request success or failure, similar to how saveAdminAccess
already implements this pattern. Verify that snackbarErrorHandler explicitly
returns a rejected Promise after displaying the error message so that downstream
catch handlers in AdminAccessListPage and other callers can properly handle the
rejection chain.
| useEffect(() => { | ||
| if (!serverErrors || Object.keys(serverErrors).length === 0) return; | ||
| formik.setErrors(serverErrors); | ||
| formik.setTouched( | ||
| Object.fromEntries(Object.keys(serverErrors).map((k) => [k, true])) | ||
| ); | ||
| }, [serverErrors]); |
There was a problem hiding this comment.
Clear Formik server errors when Redux errors reset.
Line 52 exits early for empty serverErrors, so previously injected backend errors can remain visible in Formik even after Redux clears errors back to {}.
♻️ Proposed fix
useEffect(() => {
- if (!serverErrors || Object.keys(serverErrors).length === 0) return;
- formik.setErrors(serverErrors);
+ const nextErrors = serverErrors || {};
+ formik.setErrors(nextErrors);
+ if (Object.keys(nextErrors).length === 0) return;
formik.setTouched(
- Object.fromEntries(Object.keys(serverErrors).map((k) => [k, true]))
+ Object.fromEntries(Object.keys(nextErrors).map((k) => [k, true]))
);
}, [serverErrors]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/forms/admin-access-form.js` around lines 51 - 57, The
useEffect hook in admin-access-form.js returns early when serverErrors is empty
without clearing previously set Formik errors, causing stale error messages to
persist even after Redux resets the errors. Instead of returning early when
serverErrors is empty, call formik.setErrors({}) and formik.setTouched({}) to
explicitly clear Formik's error and touched state, ensuring it stays
synchronized with the Redux state being reset.
ref: https://app.clickup.com/t/86b9n7qe1
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Style
Tests